import Token as TK
class Lexer:
    # 构造函数，初始化文件流对象
    def __init__(self, filePath):
        self.index = 1
        self.filePath = filePath
        self.tokenBuffer = ''
        try:
            self.file_ptr = open(self.filePath, 'r', encoding='utf-8')
        except IOError:
            print("Error: 没有找到文件或读取文件失败")
    # 析构函数，在词法分析正常结束时关闭流对象
    def __del__(self):
        print("析构start~ file close")
        self.file_ptr.close()
    
    
    # 读取一个字符的函数
    def getChar(self):
        # 不用做是否读到文件尾的判断，在getToken中使用额外逻辑判断
        ch = self.file_ptr.read(1)
        return ch.upper()
    

    # 回退一个字符
    def backChar(self, char):
        if char != '':
            self.file_ptr.seek(self.file_ptr.tell() - 1)

    def backMyChar(self, char, point):
        if char != '':
            self.file_ptr.seek(point)

    # 把字符加入目前的缓冲区的Token中
    def putChar(self, char):
        self.tokenBuffer += char

    
    # 判断这个token具体是tokenTable中的哪个Token，返回对应Token的信息
    # 若是查不到，返回默认错误字符
    def JudgeKeyToken(self):
        return TK.TokenTable.get(self.tokenBuffer, TK.Tokens(TK.TokenType.ERRORTOKEN, self.tokenBuffer))
    
    # -------------------------词法分析主驱动函数---------------------------------
    # 在语法分析的过程中调用此函数，每调用一次获得一个Token
    # 该函数本质是模拟DFA的一个实现，通过遍历输入流，检测所有可能的情况的Token是什么
    def getToken(self):
        # 清空上次的缓冲区
        self.tokenBuffer = ''
        # 识别到的字符
        current_char = ''
        # 最终的Token对象，在递归下降分析中进行匹配
        current_token = None
        while(1):
            # 循环控制，跳过符号前的所有的空格和换行，同时判断是否到达文件的末尾
            last_char = self.file_ptr.tell()
            current_char = self.getChar()
            # 空字符判断，读到文件的末尾
            if current_char == '':
                current_token = TK.Tokens(TK.TokenType.NONTOKEN, '')
                return current_token
            # 维护一个Index，为语法分析提供合适的报错需求接口
            elif current_char == '\n':
                self.index += 1
            # 空格的判断，处理掉所有的空格
            elif not current_char.isspace():
                break
            # current_char = pre_proccess()
        # 此时，current_char就是第一个非空白的字符(已经获取到了，不用再次获取)


        # 将此字符放入缓冲区
        self.putChar(current_char)

        # 以字母打头的字母串
        if current_char.isalpha():
            while(1):
                last_char = self.file_ptr.tell()
                current_char = self.getChar()
                # 是字符，那么放入缓冲区
                if current_char.isalnum():
                    self.putChar(current_char)
                else:
                    break
            # 结束循环时，已经移动到了当前字符的下一个字符上
            # 比如遇到的是一个空格，那么此时已经移动到了空格的下一个字符上，如果此时的字符不是一个空格的话，那么是需要一个回退的操作的
            # 当然，如果空格的下一个字符还是一个空格，那么其实这一步并不需要回退，下次再GetToken的时候会在预处理中跳过空格
            self.backMyChar(current_char, last_char)
            #self.backChar(current_char)
            # token的匹配
            current_token = self.JudgeKeyToken()
            # 匹配后，输入序列放入：
            current_token.lexeme = self.tokenBuffer
            return current_token
        
        # 以数字打头的串
        elif current_char.isdigit():
            while(1):
                last_char = self.file_ptr.tell()
                current_char = self.getChar()
                # digit+的表示
                if current_char.isdigit():
                    self.putChar(current_char)
                else:
                    break
            # '.'的识别，且注意识别.后，后面一定是digit+
            if current_char == '.':
                self.putChar(current_char)
                while(1):
                    last_char = self.file_ptr.tell()
                    current_char = self.getChar()
                    # digit+的表示
                    if current_char.isdigit():
                        self.putChar(current_char)
                    else:
                        break
            # 识别结束，回退字符
            #self.backChar(current_char)
            self.backMyChar(current_char, last_char)
            # 不同于ID，这里不用查表，直接new Token，然后放入识别到的数字
            current_token = TK.Tokens(TK.TokenType.CONST_ID, self.tokenBuffer, float(self.tokenBuffer))
            return current_token
        
        # 分隔符
        elif current_char == ';':
            return TK.Tokens(TK.TokenType.SEMICO, ";")
        elif current_char == ',':
            return TK.Tokens(TK.TokenType.COMMA, ",")
        elif current_char == '(':
            return TK.Tokens(TK.TokenType.L_BRACKET, "(")
        elif current_char == ')':
            return TK.Tokens(TK.TokenType.R_BRACKET, ")")
        
        # 用户字符串
        elif current_char == "'":
            # 先清除缓冲区中的单引号，title中不应该有引号
            self.tokenBuffer = ''
            current_char = self.getChar()
            # 遇到匹配的尾单引号时进行匹配
            while(current_char != "'" ):
                # 如果已经是空字符或者是换行了，那么应该报错，单引号匹配有错误
                if current_char == '' or current_char == '\n':
                    self.backMyChar(current_char, last_char)
                    #self.backChar(current_char)
                    return TK.Tokens(TK.TokenType.ERRORTOKEN, "缺少匹配的单引号")
                self.putChar(current_char)
                current_char = self.getChar()
            # 否则，肯定是遇到单引号，USER_STRING识别完毕。且已经移动到单引号的下一个字符，这里不用putChar
            return TK.Tokens(TK.TokenType.USER_STRING, self.tokenBuffer)
            
        # 运算符
        elif current_char == '+':
            return TK.Tokens(TK.TokenType.PLUS, "+")
        ## '-'要额外考虑是注释的情况
        elif current_char == '-':
            last_char = self.file_ptr.tell()
            current_char = self.getChar()
            if current_char == '-':
                while current_char != '\n' and current_char != '':   
                    last_char = self.file_ptr.tell()                 
                    current_char = self.getChar()
                # 不再是注释内容
                #self.backChar(current_char)
                self.backMyChar(current_char, last_char)
                # 假如就是注释，那么特别的，我们这里对getToken进行一次递归调用
                return self.getToken()
            else:
                #self.backChar(current_char)
                self.backMyChar(current_char, last_char)
                return TK.Tokens(TK.TokenType.MINUS,"-")
        ## '**'要额外考虑乘方的情况
        elif current_char == '*':
            last_char = self.file_ptr.tell()
            current_char = self.getChar()
            if current_char == '*':
                return TK.Tokens(TK.TokenType.POWER,"**")
            else:
                #self.backChar(current_char)
                self.backMyChar(current_char, last_char)
                return TK.Tokens(TK.TokenType.MUL, "*")
        ## '/'要额外考虑注释的情况
        elif current_char == '/':
            last_char = self.file_ptr.tell()
            current_char = self.getChar()
            if current_char == '/':
                while current_char != '\n' and current_char != '':
                    last_char = self.file_ptr.tell()
                    current_char = self.getChar()
                # 不再是注释
                self.backMyChar(current_char, last_char)
                #self.backChar(current_char)
                # 已经识别了是注释，那么递归调用
                return self.getToken()
            else:
                self.backMyChar(current_char, last_char)
                #self.backChar(current_char)
                return TK.Tokens(TK.TokenType.DIV, "/")
        # 其余情况为错误字符串
        else:
            return TK.Tokens(TK.TokenType.ERRORTOKEN, self.tokenBuffer)